using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using YMJake.Flowable.ExternalWorker.Client.AspNetCore.Configuration;
using YMJake.Flowable.ExternalWorker.Client.AspNetCore.Pipeline;
using YMJake.Flowable.ExternalWorker.Client.Core.Client;
using YMJake.Flowable.ExternalWorker.Client.Core.Rest;

namespace YMJake.Flowable.ExternalWorker.Client.AspNetCore.Hosting;

/// <summary>
/// Hosted service that manages pipeline-based Flowable workers
/// </summary>
public class FlowableWorkerHostedService : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;
    private readonly ILogger<FlowableWorkerHostedService> _logger;
    private readonly List<Task> _workerTasks = new();
    private readonly CancellationTokenSource _cancellationTokenSource = new();

    public FlowableWorkerHostedService(
        IServiceProvider serviceProvider,
        ILogger<FlowableWorkerHostedService> logger)
    {
        _serviceProvider = serviceProvider;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Starting Flowable Worker Hosted Service");

        try
        {
            // Get all registered worker builders
            var builders = _serviceProvider.GetServices<FlowableWorkerBuilder>().ToList();
            
            if (!builders.Any())
            {
                _logger.LogWarning("No Flowable workers configured");
                return;
            }

            _logger.LogInformation("Found {WorkerCount} configured workers", builders.Count);

            // Start workers for each builder
            foreach (var builder in builders)
            {
                StartWorker(builder, stoppingToken);
            }

            // Wait until cancellation is requested
            await Task.Delay(Timeout.Infinite, stoppingToken);
        }
        catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation("Flowable Worker Hosted Service is stopping");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error in Flowable Worker Hosted Service");
            throw;
        }
    }

    private void StartWorker(FlowableWorkerBuilder builder, CancellationToken cancellationToken)
    {
        _logger.LogInformation("Starting {Concurrency} workers for topic '{Topic}'", 
            builder.Concurrency, builder.Topic);

        // Build the pipeline once
        var pipeline = builder.BuildPipeline();

        // Start multiple concurrent workers
        for (int i = 0; i < builder.Concurrency; i++)
        {
            var workerId = i;
            var workerTask = Task.Run(async () => 
                await RunWorkerAsync(builder.Topic, pipeline, builder.PollingInterval, workerId, cancellationToken), 
                cancellationToken);
            
            _workerTasks.Add(workerTask);
        }

        _logger.LogInformation("Started {Concurrency} workers for topic '{Topic}'", 
            builder.Concurrency, builder.Topic);
    }

    private async Task RunWorkerAsync(
        string topic, 
        WorkerMiddleware pipeline, 
        TimeSpan pollingInterval, 
        int workerId, 
        CancellationToken cancellationToken)
    {
        var workerName = $"{topic}-{workerId}";
        _logger.LogDebug("Worker {WorkerName} started", workerName);

        try
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                try
                {
                    using var scope = _serviceProvider.CreateScope();
                    var restClient = scope.ServiceProvider.GetRequiredService<IFlowableExternalWorkerRestClient>();
                    var options = scope.ServiceProvider.GetRequiredService<FlowableExternalWorkerOptions>();

                    // Acquire jobs
                    var jobs = await restClient.AcquireJobs(
                        topic,
                        options.DefaultLockDuration,
                        options.DefaultNumberOfTasks,
                        options.DefaultNumberOfRetries,
                        options.WorkerId ?? Environment.MachineName);

                    if (jobs.Any())
                    {
                        _logger.LogDebug("Worker {WorkerName} acquired {JobCount} jobs", workerName, jobs.Count);

                        // Process each job
                        foreach (var job in jobs)
                        {
                            if (cancellationToken.IsCancellationRequested) break;
                            
                            await ProcessJobAsync(job, topic, pipeline, scope.ServiceProvider, restClient, cancellationToken);
                        }
                    }
                    else
                    {
                        // No jobs, wait before polling again
                        await Task.Delay(pollingInterval, cancellationToken);
                    }
                }
                catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
                {
                    break;
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Error in worker {WorkerName}", workerName);
                    await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
                }
            }
        }
        finally
        {
            _logger.LogDebug("Worker {WorkerName} stopped", workerName);
        }
    }

    private async Task ProcessJobAsync(
        IAcquiredExternalWorkerJob job,
        string topic,
        WorkerMiddleware pipeline,
        IServiceProvider serviceProvider,
        IFlowableExternalWorkerRestClient restClient,
        CancellationToken cancellationToken)
    {
        try
        {
            _logger.LogDebug("Processing job {JobId}", job.Id);

            // Create pipeline context
            var context = new WorkerPipelineContext(
                job,
                topic,
                Environment.MachineName,
                serviceProvider,
                new WorkResultBuilder(),
                cancellationToken);

            // Execute pipeline
            await pipeline(context);

            // Handle result
            await HandleResultAsync(context, restClient);

            _logger.LogDebug("Completed job {JobId}", job.Id);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to process job {JobId}", job.Id);
            
            try
            {
                await restClient.FailJob(job.Id, ex.Message, ex.ToString(), Math.Max(0, job.Retries - 1));
            }
            catch (Exception failEx)
            {
                _logger.LogError(failEx, "Failed to mark job {JobId} as failed", job.Id);
            }
        }
    }

    private async Task HandleResultAsync(IWorkerPipelineContext context, IFlowableExternalWorkerRestClient restClient)
    {
        if (context.Result == null)
        {
            // Default success
            await restClient.CompleteJob(context.Job.Id);
            return;
        }
        // Let the result handle its own execution
        await context.Result.ExecuteAsync(context.Job, restClient);
    }

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Stopping Flowable Worker Hosted Service");

        await _cancellationTokenSource.CancelAsync();

        if (_workerTasks.Any())
        {
            try
            {
                await Task.WhenAll(_workerTasks).WaitAsync(TimeSpan.FromSeconds(30), cancellationToken);
            }
            catch (TimeoutException)
            {
                _logger.LogWarning("Timeout waiting for workers to stop");
            }
            catch (OperationCanceledException)
            {
                // Expected
            }
        }

        await base.StopAsync(cancellationToken);
        _logger.LogInformation("Flowable Worker Hosted Service stopped");
    }

    public override void Dispose()
    {
        _cancellationTokenSource.Cancel();
        _cancellationTokenSource.Dispose();
        base.Dispose();
    }
}